package com.ophiux.cs.infrastructure.compoment.basedata.mybatis.interceptor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.session.RowBounds;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.dialect.Oracle10gDialect;

/**
 * Oracle 物理分页处理
 */
@Intercepts({@org.apache.ibatis.plugin.Signature(type=org.apache.ibatis.executor.Executor.class, method="query", args={MappedStatement.class, Object.class, RowBounds.class, org.apache.ibatis.session.ResultHandler.class})})
public class OracleOffsetLimitInterceptor implements Interceptor
{
	private int MAPPED_STATEMENT_INDEX;
    private int PARAMETER_INDEX;
    private int ROWBOUNDS_INDEX;
    private Dialect dialect;

    public OracleOffsetLimitInterceptor()
    { this.MAPPED_STATEMENT_INDEX = 0;
      this.PARAMETER_INDEX = 1;
      this.ROWBOUNDS_INDEX = 2; 
    }
    public Object intercept(Invocation invocation) throws Throwable
    {
      processIntercept(invocation.getArgs());
      return invocation.proceed();
    }

    private void processIntercept(Object[] queryArgs)
    {
      RowBounds rowBounds = (RowBounds)queryArgs[this.ROWBOUNDS_INDEX];
      int offset = rowBounds.getOffset();
      int limit = rowBounds.getLimit();
      if ((this.dialect.supportsLimit()) && ((offset != 0) || (limit != 2147483647)))
      {
        MappedStatement ms = (MappedStatement)queryArgs[this.MAPPED_STATEMENT_INDEX];
        Object parameter = queryArgs[this.PARAMETER_INDEX];
        boolean symbolable = this.dialect.supportsLimitOffset();
        queryArgs[this.MAPPED_STATEMENT_INDEX] = createMappedStatement(ms, symbolable, parameter, offset, limit);

        if ((symbolable) && (offset > 0)) {
          offset = 0;
        }
        limit = 2147483647;
        queryArgs[this.ROWBOUNDS_INDEX] = new RowBounds(offset, limit);
      }
    }

    private MappedStatement createMappedStatement(MappedStatement ms, boolean symbolable, 
    		Object parameter, int offset, int limit)
    {
      BoundSql boundSql = ms.getBoundSql(parameter);
      String sql = boundSql.getSql().trim();
      List list = boundSql.getParameterMappings();
      if (list.size() == 0) {
        list = new ArrayList();
      }
      if (symbolable)
        sql = this.dialect.getLimitString(sql, offset, limit);
      else {
        sql = this.dialect.getLimitString(sql, 0, limit);
      }
      sql = addParameter(list, ms, parameter, offset, limit, sql, symbolable);

      BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), sql, list, boundSql.getParameterObject());

      return copyFromMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
    }

    private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource)
    {
      MappedStatement.Builder builder = new MappedStatement.Builder(
    		  ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());

      builder.resource(ms.getResource());
      builder.fetchSize(ms.getFetchSize());
      builder.statementType(ms.getStatementType());
      builder.keyGenerator(ms.getKeyGenerator());
//      builder.keyProperty(ms.getKeyProperty());
      builder.keyProperty(ms.getKeyProperties()==null?null:ms.getKeyProperties()[0]);
      builder.timeout(ms.getTimeout());
      builder.parameterMap(ms.getParameterMap());
      builder.resultMaps(ms.getResultMaps());
      builder.resultSetType(ms.getResultSetType());
      builder.cache(ms.getCache());
      builder.flushCacheRequired(ms.isFlushCacheRequired());
      builder.useCache(ms.isUseCache());
      return builder.build();
    }
    private String addParameter(List<ParameterMapping> parameterMapping, MappedStatement ms, Object parameter, int offset, int limit, String sql, boolean symbolable)
    {
      if ((this.dialect instanceof Oracle10gDialect)) {
        if ((parameter instanceof Map)) {
          Map map = (Map)parameter;
          if ((symbolable) && (offset > 0)) {
            map.put("offset", Integer.valueOf(offset));
            map.put("limit", Integer.valueOf(offset + limit));
          } else {
            map.put("limit", Integer.valueOf(limit));
          }
        }
        parameterMapping.add(new ParameterMapping.Builder(ms.getConfiguration(), "limit", Integer.class).build());

        if ((symbolable) && (offset > 0)) {
          parameterMapping.add(new ParameterMapping.Builder(ms.getConfiguration(), "offset", Integer.class).build());
        }

      }
      else if ((this.dialect instanceof HSQLDialect)) {
        String result = sql;
        if (result.lastIndexOf("offset ?") > 0) {
          result = result.replace("offset ?", "offset " + offset);
        }
        if (result.lastIndexOf("limit ?") > 0) {
          result = result.replace("limit ?", "limit " + limit);
        }
        return result;
      }

      return sql;
    }
    public Object plugin(Object target)
    {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties)
    {
    }

   public void setDialect(Dialect dialect)
   {
     this.dialect = dialect;
   }
   private class BoundSqlSqlSource implements SqlSource
   {
        private BoundSql boundSql;
        public BoundSqlSqlSource(BoundSql boundSql)
        {
          this.boundSql = boundSql;
        }
        public BoundSql getBoundSql(Object parameterObject)
        {
          return this.boundSql;
        }
      }
   
    }
	

